package com.chuangke.file.service.impl;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chuangke.admin.data.builder.DeptDataBuilder;
import com.chuangke.admin.data.builder.UserDataBuilder;
import com.chuangke.common.constant.CacheKeyConstants;
import com.chuangke.common.constant.FileConstants;
import com.chuangke.common.constant.Masks;
import com.chuangke.common.data.builder.DataBuildConfig;
import com.chuangke.common.data.builder.DataBuilder;
import com.chuangke.common.exception.ChuangkeException;
import com.chuangke.common.idgen.IdManager;
import com.chuangke.file.dao.FileDbankMapper;
import com.chuangke.file.dao.FileInfoMapper;
import com.chuangke.file.model.FileDbank;
import com.chuangke.file.model.FileDbankSpace;
import com.chuangke.file.model.SysFilePermission;
import com.chuangke.file.service.FileDbankService;
import com.chuangke.file.service.FileDbankSpaceService;
import com.chuangke.file.service.SysFilePermissionService;

import cn.hutool.core.util.StrUtil;

/**
 * 
 * @author chuangke
 * @date "2020-09-02"
 */
@Service
public class FileDbankServiceImpl extends ServiceImpl<FileDbankMapper, FileDbank> implements FileDbankService {

	@Autowired
	private FileDbankSpaceService fileDbankSpaceService;
	@Autowired
	private FileInfoMapper fileInfoMapper;
	@Autowired
	private SysFilePermissionService sysFilePermissionService;

	@Autowired
	private CacheManager cacheManager;

	@Override
	public void regist(String belongId, String dbankType, String remarks, BigInteger limitSpace) {
		String dbankId = IdManager.nextId();
		FileDbank fileSubject = new FileDbank();
		fileSubject.setId(dbankId);
		fileSubject.setBelongId(belongId);
		fileSubject.setType(dbankType);
		fileSubject.setRemarks(remarks);
		this.save(fileSubject);

		FileDbankSpace fileDbankSpace = new FileDbankSpace();
		fileDbankSpace.setId(fileSubject.getId());
		fileDbankSpace.setLimitSpace(limitSpace);
		fileDbankSpace.setUsedSpace(new BigInteger("0"));
		fileDbankSpaceService.save(fileDbankSpace);

		SysFilePermission sysFilePermission = new SysFilePermission();
		sysFilePermission.setDbankId(dbankId);
		sysFilePermission.setAuthId(belongId);
		sysFilePermission.setAuthType(dbankType);
		sysFilePermission.setAuthRole(SysFilePermission.AUTH_ROLE_OWNER);
		sysFilePermission.setIsAuthFile(Masks.BOOL_NO);
		sysFilePermission.setCanDelete(Masks.BOOL_YES);
		sysFilePermission.setCanDownload(Masks.BOOL_YES);
		sysFilePermission.setCanEdit(Masks.BOOL_YES);
		sysFilePermission.setCanManage(Masks.BOOL_YES);
		sysFilePermission.setCanUpload(Masks.BOOL_YES);
		sysFilePermission.setCanView(Masks.BOOL_YES);
		sysFilePermissionService.save(sysFilePermission);
	}

	@Override
	@Cacheable(cacheNames = CacheKeyConstants.FILE_DBANK, key = "'" + CacheKeyConstants.FILE_DBANK + "'+#dbankId")
	public FileDbank loadDbankById(String dbankId) {
		return this.getById(dbankId);
	}

	@Override
	public Collection<FileDbank> listAuthDbanks(String userId, String dbankType) {
		List<SysFilePermission> permissionList = sysFilePermissionService.list(SysFilePermission.AUTH_FILE_TYPE_DBANK, userId);
		Map<String, FileDbank> map = new HashMap<>();
		permissionList.forEach(sysFilePermission -> {
			if (!map.containsKey(sysFilePermission.getDbankId())) {
				FileDbank dbank = ((FileDbankService) AopContext.currentProxy())
						.loadDbankWithSpaceById(sysFilePermission.getDbankId());

				if (dbank != null && (StrUtil.isBlank(dbankType) || (StrUtil.isNotBlank(dbankType) && dbankType.equals(dbank.getType())))) {
					dbank.setPermission(sysFilePermission);
					map.put(sysFilePermission.getDbankId(), dbank);
				}
			}
		});
		return map.values();
	}

	@Override
	public Collection<FileDbank> listAuthFileDbanks(String userId, String dbankType) {
		List<SysFilePermission> permissionList = sysFilePermissionService.list(SysFilePermission.AUTH_FILE_TYPE_FILE, userId);
		Map<String, FileDbank> map = new HashMap<>();
		permissionList.forEach(sysFilePermission -> {
			if (!map.containsKey(sysFilePermission.getDbankId())) {
				FileDbank dbank = ((FileDbankService) AopContext.currentProxy())
						.loadDbankWithSpaceById(sysFilePermission.getDbankId());
				//共享网盘的权限在每个文件上，不在网盘上
				if (dbank != null){
					map.put(dbank.getId(), dbank);
				}
//				if (dbank != null && (StrUtil.isBlank(dbankType)
//						|| (StrUtil.isNotBlank(dbankType) && dbankType.equals(dbank.getType())))) {
//					dbank.setPermission(sysFilePermission);
//					map.put(sysFilePermission.getDbankId(), dbank);
//				}
			}
		});
		return map.values();
	}

	@Override
	public List<FileDbank> listDbank() {
		return listDbanks(null);
	}

	@Override
	public List<FileDbank> listDbanks(String dbankType) {
		QueryWrapper<FileDbank> qw = new QueryWrapper<>();
		if (!StrUtil.isBlank(dbankType)) {
			qw.lambda().eq(FileDbank::getType, dbankType);
		}
		List<FileDbank> resultList = this.list(qw);
		
		wrapper(resultList) ;

		return resultList;
	}

	@Override
	public FileDbank loadDbankWithSpaceById(String dbankId) {
		FileDbank dbank = ((FileDbankService) AopContext.currentProxy()).loadDbankById(dbankId);
		FileDbankSpace fileDbankSpace = loadDbankSpace(dbankId);
		dbank.setLimitSpace(fileDbankSpace.getLimitSpace());
		dbank.setUsedSpace(fileDbankSpace.getUsedSpace());

		wrapper(Arrays.asList(dbank)) ;
		
		return dbank;
	}

	@Override
//	@Klock(lockType = LockType.Fair)
	public synchronized void addUsedSpace(String dbankId, BigInteger space) {
		// space小于0是删除文件，大于0是上传文件,只有上传的时候才校验空间是否充足

		FileDbankSpace systemDbankSpace = this.loadDbankSpace(FileConstants.SYSTEM_DBANK_ID);
		if (systemDbankSpace == null) {
			throw new ChuangkeException("系统网盘空间未注册");
		}

		if (space.compareTo(new BigInteger("0")) > 0
				&& systemDbankSpace.getLimitSpace().subtract(systemDbankSpace.getUsedSpace()).compareTo(space) < 0) {
			throw new ChuangkeException("系统磁盘空间不足！");
		}
		systemDbankSpace.setUsedSpace(systemDbankSpace.getUsedSpace().add(space));

		FileDbankSpace fileDbankSpace = this.loadDbankSpace(dbankId);
		if (space.compareTo(new BigInteger("0")) > 0
				&& fileDbankSpace.getLimitSpace().subtract(fileDbankSpace.getUsedSpace()).compareTo(space) < 0) {
			throw new ChuangkeException("网盘空间不足，无法上传！");
		}
		fileDbankSpace.setUsedSpace(fileDbankSpace.getUsedSpace().add(space));

		// 不必往数据库中同步,如果缓存失效，将通过file_info表，同步size到file_dbank_space中
//		cacheChannel.set(SPACE_CACHE_NAME, dbankId, fileDbankSpace);
//		cacheChannel.set(SPACE_CACHE_NAME, FileConst.SYSTEM_DBANK_ID, systemDbankSpace);
		
		cacheManager.getCache(CacheKeyConstants.FILE_DBANK_SPACE).put(dbankId, fileDbankSpace);
		cacheManager.getCache(CacheKeyConstants.FILE_DBANK_SPACE).put(FileConstants.SYSTEM_DBANK_ID, systemDbankSpace);
	}

	@Override
	public void expandSpace(String dbankId, BigInteger space) {
		fileDbankSpaceService.update(null, Wrappers.<FileDbankSpace>lambdaUpdate()
				.set(FileDbankSpace::getLimitSpace, space).eq(FileDbankSpace::getId, dbankId));
//		cacheChannel.evict(SPACE_CACHE_NAME, dbankId);
		cacheManager.getCache(CacheKeyConstants.FILE_DBANK_SPACE).evict(dbankId);
	}

	public synchronized FileDbankSpace loadDbankSpace(String dbankId) {
		ValueWrapper vw = cacheManager.getCache(CacheKeyConstants.FILE_DBANK_SPACE).get(dbankId) ;
		
		FileDbankSpace fileDbankSpace = null ;
		
		if(vw!=null) {
			fileDbankSpace = (FileDbankSpace) vw.get();
		}

		if (fileDbankSpace != null) {
			return fileDbankSpace;
		}

		initDbankUsedSpace();
		
		fileDbankSpace = (FileDbankSpace) cacheManager.getCache(CacheKeyConstants.FILE_DBANK_SPACE).get(dbankId).get();

		return fileDbankSpace;
	}

	@Override
	public void initDbankUsedSpace() {
		List<Map<String, Object>> totalFileSizeList = fileInfoMapper.selectTotalSize();
//		if (CollectionUtils.isEmpty(totalFileSizeList)) {
//			return;
//		}

		Map<String, Map<String, Object>> totalFileSizeMap = totalFileSizeList.stream()
				.collect(Collectors.toMap(data -> (String) data.get("dbank_id"), a -> a));

		BigInteger systemTotalSize = new BigInteger("0");
		FileDbankSpace systemFileDbankSpace = null;
		List<FileDbankSpace> fileDbankSpaceList = fileDbankSpaceService.list();

		for (FileDbankSpace fds : fileDbankSpaceList) {
			if (FileConstants.SYSTEM_DBANK_ID.equals(fds.getId())) {
				systemFileDbankSpace = fds;
				continue;
			}

			Map<String, Object> totalFileSize = totalFileSizeMap.get(fds.getId());
			BigInteger totalSize = null;
			if (totalFileSize == null || totalFileSize.get("total_size") == null) {
				totalSize = new BigInteger("0");
			} else {
				totalSize = new BigInteger(totalFileSize.get("total_size").toString());
			}
			fds.setUsedSpace(totalSize);
			systemTotalSize = systemTotalSize.add(totalSize);
		}
		systemFileDbankSpace.setUsedSpace(systemTotalSize);

		fileDbankSpaceService.updateBatchById(fileDbankSpaceList);

		fileDbankSpaceList.forEach(fds -> {
//			cacheChannel.set(SPACE_CACHE_NAME, fds.getId(), fds);
			cacheManager.getCache(CacheKeyConstants.FILE_DBANK_SPACE).put(fds.getId(), fds) ;
		});
	}

	private void wrapper(List<FileDbank> dbankList) {
		DataBuilder
				.of(dbankList.stream().filter(fileDbank -> FileDbank.TYPE_PERSON.equals(fileDbank.getType()))
						.collect(Collectors.toList()))
				.appendBuilder(new UserDataBuilder(), new DataBuildConfig("belongId", "name")).build();
		DataBuilder
				.of(dbankList.stream().filter(fileDbank -> FileDbank.TYPE_DEPT.equals(fileDbank.getType()))
						.collect(Collectors.toList()))
				.appendBuilder(new DeptDataBuilder(), new DataBuildConfig("belongId", "name")).build();
	}

	@Override
	public FileDbank getDbank(String type, String belongId) {
		return this.getOne(new QueryWrapper<FileDbank>().lambda().eq(FileDbank::getType, type).eq(FileDbank::getBelongId, belongId)) ;
	}
}
